/**
 * Copyright (c) 2012 Tom Nielsen. All rights reserved.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file
 */

/**
 * @fileoverview
 *  This would have been easier using a library like jquery or closure; however,
 *  we want the code to be fairly self-contained so it's easier to audit.
 */

'use strict';

/**
 * Basic left/right trim directly added to javascript's String object.
 * @return {string}
 */
String.prototype.trim = function () {
  return this.replace(/(?:(?:^|\n)\s+|\s+(?:$|\n))/g, '').replace(/\s+/g, ' ');
};


/**
 * We want to put all UI strings into the html pages of this extension to make future localization easier.
 * These are stored in hidden input fields. This function loads the element and pulls in the value from it.
 * @param elementid {string} id of the INPUT element
 */
function loadStringFromHtml(elementid) {
  var elem = document.getElementById(elementid);
  if (elem) {
    var test = elem.value.replace(/\\n/gi, '\n');
    return test;
  } else {
    return '';
  }
}


/**
 * Basic dec to hex conversion that deals with signed values.
 * @param decNum {int} 32bit number to convert
 * @return {string} Hex string without any format characters
 */
function decToHexStr(decNum) {
  if (decNum < 0) {
    decNum = 0xFFFFFFFF + decNum + 1;
  } // jscript is signed, fix that.
  return decNum.toString(16).toUpperCase();
}

/**
 * Simple formatter that takes hex strings and inserts them into a template
 * @param formatstr {string} Formatted like 'xxxx-xxxx-xxxx-xxxx-xxxx-xxxx'
 * @param hexstr {string} Hex characters in a string there should be enough
 *    letters to fill in all the x's
 * @return {string} Formatted string.
 */
function formatStrX(formatstr, hexstr) {
  var jj = 0;
  var result = [];

  for (var ii = 0; ii < formatstr.length; ii++) {
    var ch = formatstr.charAt(ii);
    if (ch == 'x') {
      if (jj > hexstr.length) {
        return result.join('');
      }

      result.push(hexstr.charAt(jj));
      jj++;
    } else {
      result.push(ch);
    }
  }

  return result.join('');
}

/**
 * load emails from page, clean them up and serialize them to localStorage
 */
function saveAccounts() {
  // edit over all input items and build string.
  var inputElems = document.getElementsByName('email');
  var resultsArr = new Array();

  for (var ii = 0; ii < inputElems.length; ii++) {
    var data = inputElems[ii].value.trim();
    if (data != '') {
      resultsArr.push(data);
    }
  }

  localStorage['emails'] = resultsArr.join('\n');
}

/**
 * user is done editing an account field. clean it up and save it
 * @param evt {Object} click event object.
 */
function handleAccountDoneButton(evt) {
  // find the edit box assocated with this button.
  var childNodes = evt.srcElement.parentNode.childNodes;
  var input = childNodes[0];
  var donebtn = childNodes[1];
  var delbtn = childNodes[2];

  input.readOnly = true;
  donebtn.hidden = true;
  delbtn.hidden = false;

  // save them all.
  saveAccounts();

  // refresh the ui on the page
  showRequiredSteps();
  input.blur();
}

/**
 * User pushed a delete button next to an account field.
 * don't really delete, just clear the email value and hide the span.
 * @param evt {Object} Click event
 */
function handleAccountDelButton(evt) {
  var span = this.parentNode;
  var input = span.childNodes[0];
  if (input) {
    input.value = '';
  }
  span.hidden = true;

  // serials and save data
  saveAccounts();

  // refresh the ui on the page
  showRequiredSteps();
}


/**
 * create <tr><td><span><input type='text' name='email'><img><img></span></td><tr>
 * building html in code like this isn't very readable. sorry.
 * the optionalDefaultValue is for code reuse. it can be called from an update event
 * or from the initial page loading code.
 * @param evt {event object} Javascript event triggering thsi code
 * @param optionalDefaultValue {string} If this is called from initialization, then this is not null
 */
function handleAddAccount(evt, optionalDefaultValue) {
  var tr = document.createElement('tr');
  var td = document.createElement('td');
  var span = document.createElement('span');
  span.className = 'accountitem';

  var input = document.createElement('input');
  input.type = 'text';
  input.name = 'email'; // used to collect all the data
  if (optionalDefaultValue) {
    input.value = optionalDefaultValue;
    input.className = 'accountinput';
    input.readOnly = true;
  } else {
    input.className = 'accountinput';
    input.maxLength = 128;

    // enter key handler in edit box
    input.addEventListener('keydown', function (evt) {
      if (evt.keyCode == 13) {
        handleAccountDoneButton(evt);
      }
    }, false);
  }

  var donebutton = document.createElement('img');
  donebutton.className = 'buttonsmall';
  donebutton.src = chrome.extension.getURL('done_button.png');
  donebutton.alt = 'done';
  donebutton.addEventListener('click', handleAccountDoneButton, false);
  if (optionalDefaultValue) {
    donebutton.hidden = true;
  }

  var delbutton = document.createElement('img');
  delbutton.className = 'buttonsmall';
  delbutton.src = chrome.extension.getURL('close_button.png');
  delbutton.alt = 'delete';
  delbutton.addEventListener('click', handleAccountDelButton, false);
  if (!optionalDefaultValue) {
    delbutton.hidden = true;
  }

  span.appendChild(input);
  span.appendChild(donebutton);
  span.appendChild(delbutton);
  td.appendChild(span);
  tr.appendChild(td);
  document.getElementById('passwordTable').appendChild(tr);
  input.focus();
}

/**
 * Shows instruction UI for helping user set things up the first time
 */
function showRequiredSteps() {
  document.getElementById('step1').hidden = (String(localStorage['user_seed'] || '').trim() != '');
  document.getElementById('step2').hidden = (String(localStorage['emails'] || '').trim() != '');
}


/**
 * Do all the logic around enabling/disabling showing/hiding ui elements.
 */
function updateFingerprintUi() {
  var seedHidden = (localStorage['hide_seed']=='true');
  var fingerprint = localStorage['user_seed'] || '';
  var fieldEmpty = (fingerprint == '');

  document.getElementById('datafingerprint').readOnly = !fieldEmpty;
  document.getElementById('resetfingerprint').hidden = fieldEmpty;
  document.getElementById('savefingerprint').hidden = !fieldEmpty;
  document.getElementById('generatefingerprint').hidden = !fieldEmpty;
  document.getElementById('hidefingerprint').hidden = seedHidden || fieldEmpty;

  var textinput = document.getElementById('datafingerprint');

  if (seedHidden) {
    // textinput.type = "password";
    textinput.placeholder = loadStringFromHtml('placeholderMessageId');
    textinput.value = '';
  } else {
    //textinput.type = "text";
    textinput.placeholder = '';
  }

}

/**
 * Clear button was pressed for the fingerprint
 * @param elem {HTMLElement} element pressed on the page like a button
 */
function handleResetFingerprint(elem) {
  var confirmed = confirm(loadStringFromHtml('clearFingerprintMessageId'));
  if (confirmed) {
    document.getElementById('datafingerprint').value = localStorage['user_seed'] = '';
    localStorage['hide_seed'] = '';
    updateFingerprintUi();
    showRequiredSteps();
  }
}

/**
 * Do all the DOM/UI work around generating a new fingerprint.
 * NOTE: Generating a unique fingerprint string uses chrome's built-in crypto library to get truely random data.
 * @param evt {Object} Mouseclick event object. unused
 */
function handleGenFingerprint(evt) {
  var RND_LENGTH = 8 * 5;

  // need to generate a unique value. Not so easy for javascript. Thankfully chrome has a good
  // native function

  // Fill typed array with 4 8-bit unsigned random integers.
  var uInt8Arr = new Uint8Array(RND_LENGTH);
  window.crypto.getRandomValues(uInt8Arr);

  // convert to a formatted string
  var hexArr = new Array();
  for (var ii = 0; ii < RND_LENGTH; ii++) {
    var char2Hex = decToHexStr(uInt8Arr[ii]);
    if (char2Hex.length == 1) hexArr.push('0');  // zero pad single digit hex numbers
    hexArr.push(char2Hex);
  }
  var datastr = formatStrX('xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx', hexArr.join(''));

  var data = document.getElementById('datafingerprint').value = localStorage['user_seed'] = datastr;
  updateFingerprintUi();  // make editable if there is no data.
  showRequiredSteps();
}

/**
 * User clicked the Hide Fingerprint button. Do the button action
 * @param evt
 */
function handleHideFingerprint(evt) {
  var confirmed = confirm(loadStringFromHtml('hideFingerprintMessageId'));
  if (confirmed) {
    localStorage['hide_seed'] = 'true';
    updateFingerprintUi();
  }
}


/**
 * Deal with saving the fingerprint field back into local storage.
 * @param evt {object} mouseclick object.
 */
function handleSaveFingerprint(evt) {
  var txtelem = document.getElementById('datafingerprint');
  var data = txtelem.value;
  localStorage['user_seed'] = data;
  localStorage['hide_seed'] = '';

  updateFingerprintUi();  // make editable if there is no data.
  showRequiredSteps();
}

/**
 * Clicking a link in the options page doesn't do anything. This function will open an new tab
 * and navitage it to the url. It assumes it's been attached to a <a> tag.
 * @param evt
 */
function handleLinkClick(evt) {
  if (this.nodeName == 'A') {
    if (this.href) {
      chrome.tabs.create({url:this.href});
    }
  }
  if (evt.preventDefault) {
    evt.preventDefault();
  }
  return false;
}

function initFingerprint() {
  var data = document.getElementById('datafingerprint').value = (localStorage['user_seed'] || '');

  document.getElementById('resetfingerprint').addEventListener('click', handleResetFingerprint, false);
  document.getElementById('savefingerprint').addEventListener('click', handleSaveFingerprint, false);
  document.getElementById('generatefingerprint').addEventListener('click', handleGenFingerprint, false);
  document.getElementById('hidefingerprint').addEventListener('click', handleHideFingerprint, false);

  updateFingerprintUi();  // make editable if there is no data.
}

function initOptions() {
  document.getElementById('addAccount').addEventListener('click', handleAddAccount, false);

  showRequiredSteps();

  initFingerprint();

  // add accounts
  var emails = (localStorage['emails'] || '').split('\n');
  for (var ii = 0; ii < emails.length; ii++) {
    handleAddAccount(null, emails[ii]);
  }

  // TODO: make generic to just gather all the links
  document.getElementById('contactlink').addEventListener('click',handleLinkClick, false);
  document.getElementById('helplink').addEventListener('click',handleLinkClick, false);
}

window.onload = initOptions;
